#include "CNodeCSG.h"
#include "IPrimitive.h"

CNodeCSG::CNodeCSG(): m_primitive(0), m_boolType(eBoolUnion)
{
	m_children[0] = 0;
	m_children[1] = 0;
}

CNodeCSG::CNodeCSG(IPrimitive* p, eBoolType b, CNodeCSG* child1, CNodeCSG* child2):
					m_primitive(p), m_boolType(b)
{
		m_children[0] = child1;
		m_children[1] = child2;				
}

CNodeCSG::~CNodeCSG()
{
	if(m_primitive != 0)
		delete m_primitive;

	if(m_children[0] != 0)
		delete m_children[0];

	if(m_children[1] != 0)
		delete m_children[1];
}

bool CNodeCSG::checkIntersection(const CRay& r, float& dist, CVector3f& normal)const
{
	dist = 0;

	//we are already in the local coordinates of the CSG (but not of the primitives)

	//leaf
	bool intersection;
	if(m_children[0] == 0 && m_children[1] == 0)
	{
		//check intersection
		intersection = m_primitive->intersect(r, dist, false);
		if(!intersection)
			return false;

		//if intersection, compute normal
		m_primitive->computeNormal(r, dist, normal);
		return true;
	}

	//branch
	switch(m_boolType)
	{
		case eBoolUnion:
			return checkIntersectionUnion(r, dist, normal);
			
		case eBoolIntersection:
			return checkIntersectionInter(r, dist, normal);

		case eBoolDifference:
			return checkIntersectionDiff(r, dist, normal);

		default:
			return false;
	}


}

bool CNodeCSG::isInside(const CVector3f& p)const
{
	//leaf
	if(m_children[0] == 0 && m_children[1] == 0)
		return m_primitive->isInside(p);

	//branch
	switch(m_boolType)
	{
		case eBoolUnion:
			if(m_children[0]->isInside(p) || m_children[1]->isInside(p))
				return true;

			return false;
			break;

		case eBoolIntersection:
			if(m_children[0]->isInside(p) && m_children[1]->isInside(p))
				return true;

			return false;
			break;

		case eBoolDifference:
			if(m_children[0]->isInside(p) && !m_children[1]->isInside(p))
				return true;

			return false;
			break;

		default:
			return false;
	}
}

void CNodeCSG::applyNullViewMatrix()
{
	if(m_primitive != 0)
	{
		CMatrix view;
		view.identity();
		m_primitive->applyViewMatrix(view);
	}

	if(m_children[0] != 0)
		m_children[0]->applyNullViewMatrix();

	if(m_children[1] != 0)
		m_children[1]->applyNullViewMatrix();
}

bool CNodeCSG::checkIntersectionUnion(const CRay& r, float& dist, CVector3f& normal)const
{
	float dist1, dist2;
	CVector3f normal1, normal2;

	bool inter1 = m_children[0]->checkIntersection(r, dist1, normal1);
	bool inter2 = m_children[1]->checkIntersection(r, dist2, normal2);

	//no intersection
	if(!inter1 && !inter2)
		return false;
	//two intersections
	else if(inter1 && inter2)
	{
		//compute the two intersections points
		CVector3f p1 = r.getOrigin() + r.getDirection() * dist1;
		CVector3f p2 = r.getOrigin() + r.getDirection() * dist2;

		//if the two points are outside surfaces
		bool p2Inside = m_children[0]->isInside(p2);
		bool p1Inside = m_children[1]->isInside(p1);

		if(dist1 == dist2)
		{
			dist = dist1;
			normal = normal1;
			return true;
		}
		if(!p2Inside && !p1Inside)
		{
			//take the closest
			if(dist1 < dist2)
			{
				dist = dist1;
				normal = normal1;
				return true;
			}
			else
			{
				dist = dist2;
				normal = normal2;
				return true;
			}
		}
		//now at least one inside
		else if(p2Inside && !p1Inside)//if p1 is outside
		{
			dist = dist1;
			normal = normal1;
			return true;
		}
		else if(p1Inside && !p2Inside) //p2 outside
		{
			dist = dist2;
			normal = normal2;
			return true;
		}
		// both inside
		else 
		{
			//get the farthest point
			float longest = dist1 < dist2? dist1 : dist2;
			longest += 0.01f;

			//calculate the new origin
			CVector3f newOrigin = r.getOrigin() + r.getDirection() * longest;

			//create new ray
			CRay newR = r;
			newR.setOrigin(newOrigin);

			//recursive call
			float newDist = 0;
			bool res = checkIntersectionUnion(newR, newDist, normal);

			//update the dist
			if(res)
				dist += (newOrigin - r.getOrigin()).norme() + newDist;

			return res;
		}
	}
	else if (inter1)//one intersection
	{
		dist = dist1;
		normal = normal1;
		return true;
	}
	else
	{
		dist = dist2;
		normal = normal2;
		return true;
	}

}

bool CNodeCSG::checkIntersectionInter(const CRay& r, float& dist, CVector3f& normal)const
{
	float dist1, dist2;
	CVector3f normal1, normal2;
	bool inter1, inter2;

	inter1 = m_children[0]->checkIntersection(r, dist1, normal1);
	inter2 = m_children[1]->checkIntersection(r, dist2, normal2);

	//no intersection
	if(!inter1 || !inter2)
	{
		return false;
	}
	else //two intersection
	{
		//get the two intersections points
		CVector3f p0 = r.getOrigin() + r.getDirection() * dist1;
		CVector3f p1 = r.getOrigin() + r.getDirection() * dist2;

		if(dist1 == dist2)
		{
			dist = dist1;
			normal = normal1;
			return true;
		}
		//if p0 is inside children1 then p0 is the intersection point
		if(m_children[1]->isInside(p0))
		{
			dist = dist1;
			normal = normal1;
			return true;
		}
		else if(m_children[0]->isInside(p1))
		{
			dist = dist2;
			normal = normal2;
			return true;
		}
		else//recursive call
		{
			//get longest distance
			float longest = dist1 < dist2? dist2 : dist1;
			longest += 0.01f;

			//compute new origin point
			CVector3f newOrigin = r.getOrigin() + r.getDirection() * longest;

			//compute new ray
			CRay newRay = r;
			newRay.setOrigin(newOrigin);

			//compute intersection
			float newDist = 0;
			bool res = checkIntersectionInter(newRay, newDist, normal);

			//set the dist
			if(res)
				dist += (r.getOrigin() - newOrigin).norme() + newDist;

			return res;
		}
	}

	//only one intersection
	//return false;
}

bool CNodeCSG::checkIntersectionDiff(const CRay& r, float& dist, CVector3f& normal)const
{
	float dist0, dist1;
	CVector3f normal0, normal1;

	//get the intersection
	bool inter0 = m_children[0]->checkIntersection(r, dist0, normal0);
	bool inter1 = m_children[1]->checkIntersection(r, dist1, normal1);

	//no intersection
	if(!inter0 && !inter1)
		return false;

	//two intersections
	else if(inter0 && inter1)
	{
		//calculate the intersection point
		CVector3f p1 = r.getOrigin() + r.getDirection() * dist1;
		CVector3f p0 = r.getOrigin() + r.getDirection() * dist0;
		if(dist1 == dist0)
		{
			dist = dist1;
			normal = normal1;
			return true;
		}
		if(dist1 < dist0)
		{
			bool p1Inside = m_children[0]->isInside(p1);
			bool p0Inside = m_children[1]->isInside(p0);
			if(p1Inside)
			{
				dist = dist1;
				normal = normal1;
				return true;
			}
			else if(!p1Inside && !p0Inside)
			{
				dist = dist0;
				normal = normal0;
				return true;
			}
			else
			{
				//get the furthest point
				float longest = 0.01f;
				if(p0Inside)
					longest += dist0;
				else 
					longest += dist1;

				//compute new origin
				CVector3f newOrigin = r.getOrigin() + r.getDirection() * longest;
				
				//create new ray
				CRay newRay = r;
				newRay.setOrigin(newOrigin);
				
				//recurisive call
				float newDist = 0;
				bool res = checkIntersectionDiff(newRay, newDist, normal);

				//update the distance
				if(res)
					dist = (newOrigin - r.getOrigin()).norme() + newDist;

				return res;
			}
		}
		else
		{
			//bool p1Inside = m_children[0]->isInside(p1);
			bool p0Inside = m_children[1]->isInside(p0);
			if(!p0Inside)
			{
				dist = dist0;
				normal = normal0;
				return true;
			}
			else
			{
				//get the furthest point
				float longest = dist0 + 0.01f;

				//compute new origin
				CVector3f newOrigin = r.getOrigin() + r.getDirection() * longest;
				
				//create new ray
				CRay newRay = r;
				newRay.setOrigin(newOrigin);
				
				//recurisive call
				float newDist = 0;
				bool res = checkIntersectionDiff(newRay, newDist, normal);

				//update the distance
				if(res)
					dist = (newOrigin - r.getOrigin()).norme() + newDist;

				return res;
			}
		}
	}
	else if(inter0)
	{
		dist = dist0;
		normal = normal0;
		return true;
	}
	else//inter1
	{
		//calculate the intersection point
		CVector3f p1 = r.getOrigin() + r.getDirection() * dist1;

		if(m_children[0]->isInside(p1))
		{
			dist = dist1;
			normal = normal1;
			return true;
		}
		else
			return false;
	}
		
	//something's wrong
	//return false;
}